<?php

/**
 * SigmaCMS - Content Management System
 *
 * Copyright (C) 2008 Pavol Biely
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package    SigmaCMS
 * @author     Pavol Biely <pavolbiely@gmail.com>
 * @copyright  2008 Pavol Biely
 * @license    http://www.gnu.org/licenses/gpl.txt   GNU General Public License
 * @link       http://pabi3.com/
 * @since      0.1.0
 */

abstract class Admin
{
	/**
	 * Action object
	 *
	 * @var object
	 */
	static private $_action;

	/**
	 * Permanently accessible sections regardless of access rights
	 * 
	 * @var array
	 */
	static private $_allowedSections = array('templates', 'logout');

	/**
	 * Display requested section of administration
	 *
	 * @param string $class
	 * @return void
	 */
	static private function action($class)
	{
		// convert to lowercase
		$class = strtolower($class);

		// does the member have access to the section?
		if (!self::hasAccessToSection($class)) {
			// send header
			header('Content-Type:text/plain; charset=utf-8');

			// username
			$member = Member::getUsername();

			// log action
			Accesslog::add(sprintf(_LOG_ADMIN_RESTRICTED, $member, $class));

			// print a message about the restricted section
			echo _ADMIN_RESTRICTED_SECTION;

			// terminate script
			exit;
		}

		// path to the administration section
		$path = 'actions/'.cfg::ADMIN_PREFIX.$class.'.php';

		// valid admin section class name
		$classname = 'Admin_'.ucfirst($class);

		// alternative path to plugin administration
		$pluginAction = substr($class,0,strlen(cfg::PLUGIN_PREFIX));
		if ($pluginAction == cfg::PLUGIN_PREFIX) {
			// administration class of plugin
			$class = substr($class,strlen(cfg::PLUGIN_PREFIX));

			// path to administration file of plugin
			$path = dirname(__FILE__).'/../'.cfg::DIR_PLUGINS.cfg::PLUGIN_PREFIX.$class.'/admin.php';

			// valid admin plugin class name
			$classname = 'Admin_Plugin_'.ucfirst($class);
		}

		// try to create new object
		try {
			// class must contain only aplhanumeric characters and dot
			if (!preg_match('/^[a-zA-Z0-9\.]+$/',$class)) {
				throw new Sigma_Exception(_ADMIN_NOTICE_ACTION_UNALLOWED_CHARS);
			}

			// it is plugin administration?
			if (stristr($classname,'Admin_Plugin_')) {
				if (!Plugin::isInstalled($class)) {
					throw new Sigma_Exception(_ADMIN_NOTICE_PLUGIN_MUST_BE_INSTALLED);
				}
			}

			if (file_exists($path)) {
				include_once $path;
			} else {
				throw new Sigma_Exception(_ADMIN_NOTICE_UNKNOWN_ACTION_CALLED);
			}

			// does the class exist?
			if (!class_exists($classname)) {
				throw new Sigma_Exception(_ADMIN_NOTICE_CLASS_NOT_FOUND);
			} else {
				// create object
				self::$_action = new $classname;

				// display specific page?
				if (isset($_GET['page'])) {
					$page = 'page_'._get('page');
					if (method_exists(self::$_action,$page)) {
						self::$_action->$page();
					} else {
						throw new Sigma_Exception(_ADMIN_NOTICE_PAGE_NOT_FOUND);
					}
				} else if (method_exists(self::$_action,'initialize')) {
					self::$_action->initialize();
				}
			}
		} catch (Sigma_Exception $e) {
			Admin::header(_ADMIN);
			echo Element::factory('h2', _ADMIN_ERROR);
			echo Element::factory('p')->class('red')->content($e->getMessage());
			Admin::footer();
		}
	}

	/**
	 * Return array of section, which do not need access rights
	 *
	 * @return array
	 */
	static public function getAllowedSections()
	{
		return self::$_allowedSections;
	}

	/**
	 * Load access rights
	 *
	 * @param integer $memberGroup
	 * @return array
	 */
	static private function loadAccessRights($memberGroup)
	{
		// this method will be cached
		static $accessRights = array();

		// already saved in cache
		if (count($accessRights) > 0) return $accessRights;

		// load access rights
		$sql = db::query("SELECT `section` FROM ".db::prefix('members_sections')." WHERE `group_id`='".intval($memberGroup)."'");
		while ($row = db::fetchObject($sql)) {
			$accessRights[] = $row->section;
		}
		db::free($sql);

		return $accessRights;
	}

	/**
	 * Check if member has access to section
	 *
	 * @param string $section
	 * @return boolean
	 */
	static private function hasAccessToSection($section)
	{
		// username
		$member = Member::getUsername();

		// group
		$memberGroup = Member::getGroup();

		// check, if member has access to this section, if he is not administrator (member "admin")
		if ($member != 'admin' && !in_array($section, self::$_allowedSections)) {
			// all groups except group with ID 1 must be checked; ID 1 is group of administrators
			if ($memberGroup != 1) {
				// access rights
				$accessRights = self::loadAccessRights($memberGroup);

				// has member access access to this section má člen právo prístupu do sekcie?
				if (!in_array($section, $accessRights)) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Display link to section, if member has access to it
	 *
	 * @param string $section
	 * @param string $lang
	 * @return string
	 */
	static private function showSectionLink($section, $lang)
	{
		if (self::hasAccessToSection($section)) {
			return "<a href='index.php?action=$section'><img src='icons/admin_$section.png' alt='$lang' width='64' height='64' /></a>$lang\n";
		} else {
			return "<img src='icons/admin_$section.png' alt='$lang' width='64' height='64' /><del>$lang</del>";
		}
	}

	/**
	 * Initialization
	 *
	 * @return void
	 */
	static public function initialize()
	{
		// INI tweak
		ini_set('session.use_only_cookies','1');
		ini_set('arg_separator.output','&amp;');
		ini_set('url_rewriter.tags','a=href,form=action,img=src');

		// session name
		session_name('sigma_admin');

		// start session
		session_start();

		// GZIP compression
		if (extension_loaded('zlib')) {
			ob_start('ob_gzhandler');
		} else {
			ob_start();
		}

		// initialization of whole application
		Main::initialize();

		// is member logged in?
		if (!Member::isLoggedIn()) {
			Admin::header(_SIGMACMS,_ADMIN_AUTHORIZATION);

			// log in form
			self::login();

			Admin::footer();
		} else {
			// extend expiration
			Member::updateSession();

			// check if token is valid
			if (!isset($_GET['token']) || (_get('token') != Member::getToken())) {
				// log out member
				Member::logOut();

				// redirect without token
				Admin::redirect('index.php',false);
			}

			// add security token to every link, form and image
			output_add_rewrite_var('token',Member::getToken());

			// section of administration
			if (isset($_GET['action'])) {
				// create new object
				self::action(_get('action'));
			} else {
				Admin::header(_ADMIN);
	
				echo "<h2>"._ADMIN."</h2>\n";

				echo "<table class='index'>\n";

				echo "<colgroup align='center'>\n";
				echo "<col width='25%' />\n";
				echo "<col width='25%' />\n";
				echo "<col width='25%' />\n";
				echo "<col width='25%' />\n";
				echo "</colgroup>\n";

				echo "<tr>\n";
				echo "<td>".self::showSectionLink('plugins',_ADMIN_PLUGINS)."</td>\n";
				echo "<td>".self::showSectionLink('skins',_ADMIN_SKINS)."</td>\n";
				echo "<td>".self::showSectionLink('filemanager',_ADMIN_FILEMANAGER)."</td>\n";
				echo "<td>".self::showSectionLink('members',_ADMIN_MEMBERS)."</td>\n";
				echo "</tr>\n";

				echo "<tr>\n";
				echo "<td>".self::showSectionLink('languages',_ADMIN_LANGUAGES)."</td>\n";
				echo "<td>".self::showSectionLink('configuration',_ADMIN_CONFIGURATION)."</td>\n";
				echo "<td>".self::showSectionLink('backup',_ADMIN_BACKUP)."</td>\n";
				echo "<td>".self::showSectionLink('logout',_ADMIN_LOGOUT)."</td>\n";
				echo "</tr>\n";

				echo "</table>\n";

				// compact plugin menu
				$sql = db::query("SELECT `id`,`name` FROM ".db::prefix('plugins')." WHERE `active`='1' ORDER BY `name` ASC");
				if (db::numRows($sql) > 0) {
					$i = 0;
					while ($row = db::fetchObject($sql)) {
						$className  = 'Admin_Plugin_';
						$className .= ucfirst(strtolower($row->id));
						if (Member::hasAccess($className)) {
							if (method_exists($className,'initialize')) {
								if ($i++ > 0) {
									echo ", ";
								} else {
									echo "<h2>"._ADMIN_PLUGINS."</h2>\n";
								}
								echo "<a href='index.php?action=plugin.".$row->id."'>"._hsc($row->name)."</a>\n";
							}
						}
					}
					echo "\n";
				}
				db::free($sql);

				// can member see logs?
				if (self::hasAccessToSection('logs')) {
					// logs path
					$logsPath = dirname(__FILE__).'/../'.cfg::DIR_LOGS;
	
					// parameter for glob
					$globParam = $logsPath.'*.log';
	
					// glob
					$glob = glob($globParam);

					if (is_array($glob)) {
						if (count($glob) > 0) {
							echo "<h2>"._ADMIN_LOGS."</h2>\n";
							echo "<ul>";
							foreach ($glob as $log) {
								$filename = basename($log);
								echo "<li><a href='index.php?action=logs&amp;filename=$filename'>$filename</a></li>\n";
							}
							echo "</ul>";
						}
					}
				}

				Admin::footer();
			}
		}

		// clear buffer
		ob_end_flush();
	}

	/**
	 * Administration header
	 *
	 * @return void
	 */
	static public function header()
	{
		// this method can be called only once
		static $called = false;
		if (!$called) $called = true; else return;

		// event before skin
		Admin::addEvent('preAdminSkin');

		// send Content-Type header
		Main::sendContentType('application/xhtml+xml');

		// split parameters by dash
		$titles = func_get_args();
		array_walk($titles,'_hsc');
		$titles = implode(' &ndash; ',$titles);

		$header =
		"<?xml version='1.0' encoding='utf-8'?>\n".
		"<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.1//EN' 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'>\n".
		"<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='"._hsc(cfg::get('language'))."'>\n".
		"<head>\n".
		"<meta http-equiv='Content-Type' content='application/xhtml+xml; charset=utf-8' />\n".
		"<meta http-equiv='ImageToolbar' content='no' />\n".
		"<meta http-equiv='Cache' content='no-cache, must-revalidate' />\n".
		"<meta http-equiv='Pragma' content='no-cache' />\n".
		"<meta http-equiv='Expires' content='-1' />\n".
		"<meta name='robots' content='noindex,nofollow' />\n".
		"<base href='".cfg::URL.cfg::DIR_ADMIN."' />\n";

		// add event for scripts, links, etc.
		ob_start();
		Admin::addEvent('adminSkinHeader');
		$header .= ob_get_contents();
		ob_end_clean();

		// CSS for administration
		$adminCSS = Media::clearPath(cfg::get('skin_admin'));

		// CSS not found?
		if (!file_exists(dirname(__FILE__).'/skin/'.$adminCSS) && $adminCSS != 'default.css') {
			// log - skin was not found
			Actionlog::add(sprintf(_LOG_ADMIN_ADMINSKIN_NOT_FOUND,$adminCSS));

			// set default.css as an admin skin
			db::query("UPDATE ".db::prefix('configuration')." SET `value`='default.css' WHERE `name`='skin_admin'");

			// set default.css
			$adminCSS = 'default.css';
		}

		$header .= "<script src='skin/admin.js' type='text/javascript'></script>\n".
		"<link rel='stylesheet' href='skin/"._hsc($adminCSS)."' type='text/css' media='all' />\n".
		"<title>".$titles."</title>\n".
		"</head>\n".
		"<body>\n\n".
		"<div class='main'>\n".
		"<div class='nav'>\n".
		"<div class='left'>".
		"<a href='index.php'>"._ADMIN."</a> /\n".
		"<a href='".cfg::URL."'>"._ADMIN_SITE."</a>\n".
		"</div>\n".
		"<div class='right'>";

		if (Member::isLoggedIn()) {
			$header .= _ADMIN_LOGGEDINAS."\n";
			$header .= "<a href='index.php?action=members&amp;page=edit&amp;username=".Member::getUsername()."'>".Member::getUsername()."</a>\n";
			$header .= "("._SIGMACMS." ".cfg::VERSION.")\n";
		}

		$header .= "</div>\n".
		"<div class='clear-both'></div>\n".
		"</div>\n".
		"<div class='pad'>\n\n".
		"<div class='admin-right'>\n";

		// is member logged in?
		if (Member::isLoggedIn()) {
			// display menu
			if (is_object(self::$_action)) {
				$header .=
				"<h2>"._ADMIN_OPTIONS."</h2>\n".
				"<ul>\n".
				"<li><a href='index.php' title='"._ADMIN_RETURN."'>"._ADMIN_RETURN."</a></li>\n";
	
				if (isset($_GET['action'])) {
					$action   = _get('action');
					$constant = '_ADMIN_'.strtoupper($action);
					if (defined($constant)) {
						$header .= "<li><a href='index.php?action=$action' title='".constant($constant)."'>".constant($constant)."</a></li>\n";
					} else if (preg_match('/^plugin\./',$_GET['action'])) {
						$header .= "<li><a href='index.php?action=plugins' title='"._ADMIN_PLUGINS."'>"._ADMIN_PLUGINS."</a></li>\n";
					}
				}
	
				$header .= "</ul>\n";
	
				// does menu exist?
				if (method_exists(self::$_action,'menu')) {
					ob_start();
					self::$_action->menu();
					$header .= ob_get_contents();
					ob_end_clean();
				}
			} else {
				$header .= "<h2>"._ADMIN_OPTIONS."</h2>\n<ul>\n";

				$menu = array(
				'plugins'       => _ADMIN_PLUGINS,
				'skins'         => _ADMIN_SKINS,
				'languages'     => _ADMIN_LANGUAGES,
				'filemanager'   => _ADMIN_FILEMANAGER,
				'members'       => _ADMIN_MEMBERS,
				'configuration' => _ADMIN_CONFIGURATION,
				'backup'        => _ADMIN_BACKUP);
				foreach ($menu as $action => $name) {
					if (self::hasAccessToSection($action)) {
						$header .= "<li><a href='index.php?action=$action'>$name</a></li>\n";
					}
				}

				$header .= "</ul>\n";
			}

			$header .=
			"<h2>"._ADMIN_AUTHORIZATION."</h2>\n".
			"<ul>\n".
			"<li><a href='index.php?action=logout' title='"._ADMIN_LOGOUT."'>"._ADMIN_LOGOUT."</a></li>\n".
			"</ul>\n";
		} else {
			$header .=
			"<h2>"._ADMIN_NOTICE."</h2>\n".
			"<p>"._ADMIN_INFO."</p>\n";
		}

		$header .= "</div>\n<div class='admin-left'>\n";

		Parser::singleton()->analyze($header);
	}

	/**
	 * Administration footer
	 *
	 * @return void
	 */
	static public function footer()
	{
		// this method can be called just once
		static $called = false;
		if (!$called) $called = true; else return;

		$footer =
		"</div>\n</div>\n".
		"\n<div class='footer clear-both'>\n".
		_ADMIN_COPYRIGHT."\n".
		"</div>\n".
		"\n</div>\n\n".
		"\n</body>\n".
		"</html>\n";

		Parser::singleton()->analyze($footer);

		// events after skin
		Admin::addEvent('posAdminSkin');
	}

	/**
	 * Create <img /> element
	 *
	 * @param string $path
	 * @param string $desc
	 * @return string
	 */
	static public function image($path, $desc)
	{
		if (preg_match('/^[a-zA-Z0-9-_]+$/',$path)) {
			$img = Element::factory('img')->src('icons/'.$path.'.png')->alt($desc)->title($desc)->width(16)->height(16)->nl(0);
			return $img->getContent();
		}
	}

	/**
	 * Redirect (only relative path)
	 *
	 * @param string $path
	 * @param boolean $token
	 * @return void
	 */
	static public function redirect($path, $token = true)
	{
		// absolute paths are not allowed
		if (!preg_match('/^(http|https|ftp|ftps)/',$path)) {
			// remove unnecessary characters
			$path = str_replace(array("\n","\r"),NULL,$path);

			// add token?
			if ($token) {
				if (strstr($path,'?')) {
					$tokenURL = '&token='.Member::getToken();
				} else {
					$tokenURL = '?token='.Member::getToken();
				}
			} else {
				$tokenURL = NULL;
			}

			// send header
			header('Location:'.cfg::URL.cfg::DIR_ADMIN.$path.$tokenURL);

			exit;
		}
	}

	/**
	 * Confirmation
	 *
	 * @param string $url
	 * @param string $question
	 * @return void
	 */
	static public function confirm($url = 'index.php', $question = _ADMIN_SURE)
	{
		echo Element::factory('h2', $question);

		$div = Element::factory('div')->class('sure');
		$div->append('a', _ADMIN_SURE_YES)->href($url)->title(_ADMIN_SURE_YES);
		$div->append('a', _ADMIN_SURE_NO)->href('javascript:history.back()')->title(_ADMIN_SURE_NO);
		echo $div;
	}

	/**
	 * Log in form
	 *
	 * @return void
	 */
	static private function login()
	{
		if (isset($_POST['username'],$_POST['password'])) {
			// log in member
			$logIn = Member::logIn(_post('username'),_post('password'));

			// redirect
			if ($logIn) {
				Admin::redirect('index.php');
			}
		}

		echo Element::factory('h2', _ADMIN_AUTHORIZATION);

		$form     = Element::factory('form')->action('index.php')->method('post')->class('login')->onsubmit('return checkLogIn()');
		$fieldset = Element::factory('fieldset');

		// username
		$fieldset->append('label')->for('username')->content(Admin::image('member',_ADMIN_USERNAME)." "._ADMIN_USERNAME, true);
		$fieldset->append('input')->id('username')->name('username')->type('text')->maxlength('20');

		// password
		$fieldset->append('label')->for('password')->content(Admin::image('key',_ADMIN_PASSWORD)." "._ADMIN_PASSWORD, true);
		$fieldset->append('input')->id('password')->name('password')->type('password');

		// submit button
		$fieldset->append('input')->type('submit')->value(_ADMIN_LOGIN);

		$form->append($fieldset);
		echo $form;
	}

	/**
	 * Add event to specified place
	 *
	 * @param string $name
	 * @return void
	 */
	static public function addEvent($name)
	{
		// load additional parameters
		$params = func_get_args();
		
		// skip the first parameter
		array_shift($params);

		// registered plugins
		$plugins = Plugin::getPlugins();

		// cached instances of plugins
		static $pluginObject = array();

		// method/event name
		$pluginEvent = 'event_'.$name;

		// load registered plugins
		foreach ($plugins as $pluginId => $instance) {
			// is instance saved in the cache?
			if (isset($pluginObject[$pluginId])) {
				// does event exist?
				if (method_exists($pluginObject[$pluginId],$pluginEvent)) {
				    call_user_func_array(array($pluginObject[$pluginId],$pluginEvent),$params);
				}
				// skip this cycle
				continue;
			}

			// administration file
			$pluginAdmin = dirname(__FILE__).'/../'.cfg::DIR_PLUGINS.cfg::PLUGIN_PREFIX.$pluginId.'/admin.php';

			// does the administration file exist?
			if (file_exists($pluginAdmin)) {
				// plugin admin class
				$pluginClass = 'Admin_Plugin_'.ucfirst(strtolower($pluginId));

				include_once $pluginAdmin;

				// does the plugin admin class exist?
				if (class_exists($pluginClass)) {
					// create a new object
					$pluginObject[$pluginId] = new $pluginClass;

					// does the event exist?
					if (method_exists($pluginObject[$pluginId],$pluginEvent)) {
					    call_user_func_array(array($pluginObject[$pluginId],$pluginEvent),$params);
					}
				}
			}
		}
	}

	/**
	 * Create instance of Admin_Templates class
	 *
	 * @return object
	 */
	static public function templates()
	{
		// path to templates
		$path  = 'actions/'.cfg::ADMIN_PREFIX.'templates.php';
		
		// include template administration
		include_once $path;

		return new Admin_Templates();
	}
}
